package listeners

import (
	"blockchains-aio/conf"
	"blockchains-aio/db"
	"blockchains-aio/stack"
	"blockchains-aio/util"
	"fmt"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	"os"
	"strconv"
)

// Note:
// be used to replace stack.go in order to support docker swarm
func RegisterStackCluster(router *gin.Engine) {
	router.MaxMultipartMemory = 8 << 10 // 8 KB
	rootGroup := getRootGroup(router)

	clusterStack := rootGroup.Group("/stack/cluster")
	{
		// poa cluster on top of swarm
		clusterStack.POST("/poa/tmpl", doGenPoaClusterConfig)
		clusterStack.POST("/poa", doDeployForPoaCluster)

		// pow cluster on top of swarm
		clusterStack.POST("/pow/tmpl", doGenPowClusterConfig)
		clusterStack.POST("/pow", doDeployForPowCluster)

		// pos cluster on top of swarm
		clusterStack.POST("/pos/tmpl", doGenPosClusterConfig)
		clusterStack.POST("/pos", doDeployForPosCluster)

		clusterStack.DELETE("/instance", doDeleteStackInstance)
	}

}

func doDeployForPosCluster(ctx *gin.Context) {
	chainname := ctx.PostForm("chainname")
	nodeNum := ctx.PostForm("nodeNum")
	consensus := ctx.PostForm("consensus") // pos

	log.Printf("recieved {chainname:%s,nodeNum:%s,consensus:%s}", chainname, nodeNum, consensus)

	if len(chainname) == 0 || len(nodeNum) == 0 {
		//ctx.JSON(400, fmt.Sprintf("Failed to get chainname or nodeNum"))
		ctx.JSON(400, NewErrorMessage(Stack, 1, "doDeployForPosCluster", fmt.Sprintf("Failed to get chainname or nodeNum")).Json())
		return
	}
	if _, err := strconv.Atoi(nodeNum); err != nil {
		//ctx.JSON(400, fmt.Sprintf("%v is not a number", nodeNum))
		ctx.JSON(400, NewErrorMessage(Stack, 2, "doDeployForPosCluster", fmt.Sprintf("%v is not a number", nodeNum)).Json())
		return
	}
	nodeNumber, _ := strconv.Atoi(nodeNum)
	if len(consensus) == 0 {
		consensus = "pos"
	}

	file, _ := ctx.FormFile("file")
	f, err := file.Open()
	if err != nil {
		//ctx.JSON(400, fmt.Sprintf("Failed to open yaml:%v", err))
		ctx.JSON(400, NewErrorMessage(Stack, 3, "doDeployForPosCluster", fmt.Sprintf("Failed to open yaml:%v", err)).Json())
		return
	}
	defer f.Close()

	p := make([]byte, file.Size)
	if _, err := f.Read(p); err != nil {
		//ctx.JSON(400, fmt.Sprintf("Failed to read yaml: %v", err))
		ctx.JSON(400, NewErrorMessage(Stack, 4, "doDeployForPosCluster", fmt.Sprintf("Failed to read yaml: %v", err)).Json())
		return
	}

	if err := stack.MkChainDirAndCopyDirs_POS2(chainname, consensus); err != nil {
		//ctx.JSON(400, fmt.Sprintf("Failed to copy dirs: %v", err))
		ctx.JSON(400, NewErrorMessage(Stack, 5, "doDeployForPosCluster", fmt.Sprintf("Failed to copy dirs: %v", err)).Json())
		return
	}

	chainFolder, _ := util.ExpandTilde(fmt.Sprintf("%s/%s", conf.GetNfsFolder(), chainname))
	//yaml := chainFolder + "/" + chainname + ".yml"
	yaml := chainFolder + "/" + conf.DefaultComposeFileName

	if err := os.WriteFile(yaml, p, 0644); err != nil {
		// ctx.JSON(400, fmt.Sprintf("Failed to write file %s: %v", yaml, err))
		ctx.JSON(400, NewErrorMessage(Stack, 6, "doDeployForPosCluster", fmt.Sprintf("Failed to write file %s: %v", yaml, err)).Json())
		return
	}

	log.Printf(fmt.Sprintf("working directory: %s", chainFolder))
	out, err := util.DockerStackDeploy(yaml, chainname, chainFolder)
	if err != nil {
		//ctx.JSON(400, fmt.Sprintf("Failed to deploy stack: %v", err))
		ctx.JSON(http.StatusInternalServerError, NewErrorMessage(Stack, conf.BrindDownContainersInCompose, "doDockStack2DeployForPos", fmt.Sprintf("Failed to deploy stack: %v", err)).Json())
		return
	}

	if err = db.CreateStack(chainname, consensus, string(p), nodeNumber); err != nil {
		//ctx.JSON(http.StatusBadRequest, fmt.Sprintf("Failed to store the stack{chainname:%s, consensus:%s, config:%s, nodeNumber:%v}: %v", chainname, consensus, string(p), nodeNumber, err))
		ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 7, "doDockStack2DeployForPos", fmt.Sprintf("Failed to store the stack{chainname:%s, consensus:%s, config:%s, nodeNumber:%v}: %v", chainname, consensus, string(p), nodeNumber, err)).Json())
		return
	}

	ctx.JSON(200, string(out))
}

func doGenPosClusterConfig(ctx *gin.Context) {
	var config stack.SoloPOS
	consensus := ctx.GetHeader("Consensus-Template-Type") //pos

	if err := ctx.BindJSON(&config); err != nil {
		ctx.JSON(http.StatusBadRequest, err)
		return
	}
	config.Nfs, _ = util.ExpandTilde(conf.GetNfsFolder())

	posTmpl := fmt.Sprintf("%s/%s.tmpl", conf.GetStackTemplateFolder(), consensus)
	expendedPoaTmpl, _ := util.ExpandTilde(posTmpl)
	err := stack.ParsePOSTemplate(ctx.Writer, expendedPoaTmpl, config)
	if err != nil {
		//ctx.JSON(http.StatusBadRequest, fmt.Sprintf("%v", err))
		ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 1, "doGenStackConfigForNewPos", fmt.Sprintf("%v", err)).Json())
	}
}

func doDeleteStackInstance(ctx *gin.Context) {
	stackIdStr := ctx.Query("stackId")
	chainname := ctx.Query("chain")
	log.Printf("stackId: %s, chain: %s", stackIdStr, chainname)
	if stackIdStr == "" && chainname == "" {
		ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 1, "doDeleteStackInstance", fmt.Sprintf("bad parameters: stackId or chainname")).Json())
		return
	}

	if stackIdStr != "" {
		stack := db.GetStackById(stackIdStr)
		if stack == nil {
			ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 2, "doDeleteStackInstance", fmt.Sprintf("found no record: stackId")).Json())
			return
		}

		chainname = stack.ChainName
		log.Printf("find chainname in the db: %s", chainname)
	}

	chainFolder, _ := util.ExpandTilde(fmt.Sprintf("%s/%s", conf.GetNfsFolder(), chainname))
	theChainYaml := chainFolder + "/" + chainname + ".yml"
	log.Println("Path existed or not:", theChainYaml)
	if util.IsPathExists(theChainYaml) {
		//ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 3, "doDeleteStack2Instance", fmt.Sprintf("bad parameters: %s is unavailable", theChainYaml)).Json())
		//return
		var _, err = util.DockerStackRm(chainFolder, chainname)
		if err != nil {
			ctx.JSON(http.StatusInternalServerError, NewErrorMessage(Stack, 4, "doDeleteStackInstance", fmt.Sprintf("Failed to bring down compose task: %v", err)).Json())
			return
		}

		err = os.RemoveAll(chainFolder)
		if err != nil {
			//ctx.JSON(http.StatusInternalServerError, NewErrorMessage(Stack, 6, "doDeleteStackInstance", fmt.Sprintf("Failed to remove chain folder: %v", err)).Json())
			//return
			// "Failed to remove chain folder: unlinkat ...: directory not empty"
			log.Println("warning:", fmt.Sprintf("%v", err))
		}

	}

	err := db.DelStackById(stackIdStr)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, NewErrorMessage(Stack, 5, "doDeleteStack2Instance", fmt.Sprintf("Failed to delete record: %v", err)).Json())
		return
	}

	ctx.JSON(http.StatusOK, "")
}

func doGenPowClusterConfig(ctx *gin.Context) {
	var powConfig stack.POW
	consensus := ctx.GetHeader("Consensus-Template-Type") //pow

	if err := ctx.BindJSON(&powConfig); err != nil {
		ctx.JSON(http.StatusBadRequest, err)
		return
	}

	powTmpl := fmt.Sprintf("%s/%s.tmpl", conf.GetStackTemplateFolder(), consensus)
	err := stack.ParsePOATemplate(ctx.Writer, powTmpl, powConfig)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("%v", err))
	}
}

func doDeployForPowCluster(ctx *gin.Context) {
	chainname := ctx.PostForm("chainname")
	nodeNum := ctx.PostForm("nodeNum")
	consensus := ctx.PostForm("consensus") // pow

	log.Printf("recieved {chainname:%s,nodeNum:%s,consensus:%s}", chainname, nodeNum, consensus)

	if len(chainname) == 0 || len(nodeNum) == 0 {
		ctx.JSON(400, fmt.Sprintf("Failed to get chainname or nodeNum"))
		return
	}
	if _, err := strconv.Atoi(nodeNum); err != nil {
		ctx.JSON(400, fmt.Sprintf("%v is not a number", nodeNum))
		return
	}
	nodeNumber, _ := strconv.Atoi(nodeNum)
	if len(consensus) == 0 {
		consensus = "pow"
	}

	file, _ := ctx.FormFile("file")
	f, err := file.Open()
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to open yaml:%v", err))
		return
	}
	defer f.Close()

	p := make([]byte, file.Size)
	if _, err := f.Read(p); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to read yaml: %v", err))
		return
	}

	if err := stack.CopyDirs(chainname, "pow2"); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to copy dirs: %v", err))
		return
	}

	if err := stack.BuildPow2NodesDir(nodeNumber, chainname, "poa2"); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to copy dirs: %v", err))
		return
	}

	//yamlWithPath := hardCodedPath + "/" + chainname
	//yamlWithPath := conf.GetStackConfigFolder() + "/" + chainname
	chainFolder, _ := util.ExpandTilde(fmt.Sprintf("%s/%s", conf.GetNfsFolder(), chainname))
	yaml := chainFolder + "/" + chainname + ".yml"

	//log.Println(string(p))
	//ctx.JSON(200, string(p))
	if err := os.WriteFile(yaml, p, 0644); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to write file %s: %v", yaml, err))
		return
	}

	log.Printf(fmt.Sprintf("working directory: %s", chainFolder))
	out, err := util.DockerStackDeploy(yaml, chainname, chainFolder)
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to deploy stack: %v", err))
		return
	}

	if err = db.CreateStack(chainname, consensus, string(p), nodeNumber); err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("Failed to store the stack{chainname:%s, consensus:%s, config:%s, nodeNumber:%v}: %v", chainname, consensus, string(p), nodeNumber, err))
		return
	}

	ctx.JSON(200, string(out))
}

func doGenPoaClusterConfig(ctx *gin.Context) {
	var poaConfig stack.POA
	consensus := ctx.GetHeader("Consensus-Template-Type") // poa

	if err := ctx.BindJSON(&poaConfig); err != nil {
		//ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		ctx.JSON(http.StatusBadRequest, err)
		return
	}
	poaConfig.Nfs, _ = util.ExpandTilde(conf.GetNfsFolder())

	poaTmpl := fmt.Sprintf("%s/%s.tmpl", conf.GetStackTemplateFolder(), consensus)
	expendedPoaTmpl, _ := util.ExpandTilde(poaTmpl)
	err := stack.ParsePOATemplate(ctx.Writer, expendedPoaTmpl, poaConfig)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("%v", err))
	}
}

func doDeployForPoaCluster(ctx *gin.Context) {
	var (
		hasGenesis = false
	)
	chainname := ctx.PostForm("chainname")
	nodeNum := ctx.PostForm("nodeNum")
	consensus := ctx.PostForm("consensus") // poa

	log.Printf("recieved {chainname:%s,nodeNum:%s,consensus:%s}", chainname, nodeNum, consensus)

	if len(chainname) == 0 || len(nodeNum) == 0 {
		ctx.JSON(400, fmt.Sprintf("Failed to get chainname or nodeNum"))
		return
	}
	if _, err := strconv.Atoi(nodeNum); err != nil {
		ctx.JSON(400, fmt.Sprintf("%v is not a number", nodeNum))
		return
	}
	nodeNumber, _ := strconv.Atoi(nodeNum)
	if len(consensus) == 0 {
		consensus = "poa"
	}

	file, _ := ctx.FormFile("file")
	f, err := file.Open()
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to open yaml:%v", err))
		return
	}
	defer f.Close()
	genesisBytes, err := readFormFileBytes(ctx, "genesis")
	if err != nil {
		log.Printf("warning: an exception was ignored while doing custom genesis: %v", err)
	}
	if len(genesisBytes) > 0 {
		hasGenesis = true
		log.Println("custom genesis size:", len(genesisBytes))
	}

	p := make([]byte, file.Size)
	if _, err := f.Read(p); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to read yaml: %v", err))
		return
	}

	if err := stack.CopyDirs_POA2(chainname, "poa2"); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to copy dirs: %v", err))
		return
	}

	if err := stack.BuildPoa2NodesDir(nodeNumber, chainname, "poa2"); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to copy dirs: %v", err))
		return
	}

	//yamlWithPath := hardCodedPath + "/" + chainname
	//yamlWithPath := conf.GetStackConfigFolder() + "/" + chainname
	chainFolder, _ := util.ExpandTilde(fmt.Sprintf("%s/%s", conf.GetNfsFolder(), chainname))
	yaml := chainFolder + "/" + chainname + ".yml"
	genesis := chainFolder + "/" + "config/genesis.json"

	//log.Println(string(p))
	//ctx.JSON(200, string(p))
	if err := os.WriteFile(yaml, p, 0644); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to write file %s: %v", yaml, err))
		return
	}

	if hasGenesis {
		if err := os.WriteFile(genesis, genesisBytes, 0644); err != nil {
			ctx.JSON(400, fmt.Sprintf("Failed to write custom genesis file %s: %v", genesis, err))
			return
		}
	}

	log.Printf(fmt.Sprintf("working directory: %s", chainFolder))
	out, err := util.DockerStackDeploy(yaml, chainname, chainFolder)
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to deploy stack: %v", err))
		return
	}

	if err = db.CreateStack(chainname, consensus, string(p), nodeNumber); err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("Failed to store the stack{chainname:%s, consensus:%s, config:%s, nodeNumber:%v}: %v", chainname, consensus, string(p), nodeNumber, err))
		return
	}

	ctx.JSON(200, string(out))
}
